use crate::Error;
use casbin::{error::AdapterError, Result};
use rbatis::crud::{CRUD, CRUDMut, Skip};
use rbatis::db::DBExecResult;
use rbatis::DriverType;
use rbatis::rbatis::Rbatis;
use rbatis::wrapper::Wrapper;
use lazy_static::lazy_static;

use crate::{
    adapter::TABLE_NAME,
    models::{CasbinRule},
};

// #[cfg(feature = "postgres")]
// pub type Connection = diesel::PgConnection;
// #[cfg(feature = "mysql")]
// pub type Connection = diesel::MysqlConnection;
// #[cfg(feature = "sqlite")]
// pub type Connection = diesel::SqliteConnection;
//
// type Pool = PooledConnection<ConnectionManager<Connection>>;

lazy_static! {
  // Rbatis是线程、协程安全的，运行时的方法是Send+Sync，无需担心线程竞争
  pub static ref ADAPTER_RB:Rbatis=Rbatis::new();
}

#[cfg(feature = "postgres")]
pub async fn new(r: &Rbatis) -> std::result::Result<DBExecResult, rbatis::Error> {
    let sql = format!(
        r#"
                CREATE TABLE IF NOT EXISTS {} (
                    id SERIAL PRIMARY KEY,
                    ptype VARCHAR NOT NULL,
                    v0 VARCHAR NOT NULL,
                    v1 VARCHAR NOT NULL,
                    v2 VARCHAR NOT NULL,
                    v3 VARCHAR NOT NULL,
                    v4 VARCHAR NOT NULL,
                    v5 VARCHAR NOT NULL,
                    CONSTRAINT unique_key_diesel_adapter UNIQUE(ptype, v0, v1, v2, v3, v4, v5)
                );
            "#,
        TABLE_NAME
    ).as_str();
    r.exec(&sql, Vec::new()).await
}

#[cfg(feature = "mysql")]
pub async fn new(r: &Rbatis) -> std::result::Result<DBExecResult, rbatis::Error> {
    let sql = format!(
        r#"
                CREATE TABLE IF NOT EXISTS {} (
                    id INT NOT NULL AUTO_INCREMENT,
                    ptype VARCHAR(12) NOT NULL,
                    v0 VARCHAR(128) NOT NULL,
                    v1 VARCHAR(128) NOT NULL,
                    v2 VARCHAR(128) NOT NULL,
                    v3 VARCHAR(128) NOT NULL,
                    v4 VARCHAR(128) NOT NULL,
                    v5 VARCHAR(128) NOT NULL,
                    PRIMARY KEY(id),
                    CONSTRAINT unique_key_diesel_adapter UNIQUE(ptype, v0, v1, v2, v3, v4, v5)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
            "#,
        TABLE_NAME
    );
    r.exec(&sql, Vec::new()).await
}

#[cfg(feature = "sqlite")]
pub async fn new(r: &Rbatis) -> std::result::Result<DBExecResult, rbatis::Error> {
    let sql = format!(
        r#"
                CREATE TABLE IF NOT EXISTS {} (
                    id INTEGER PRIMARY KEY,
                    ptype VARCHAR(12) NOT NULL,
                    v0 VARCHAR(128) NOT NULL,
                    v1 VARCHAR(128) NOT NULL,
                    v2 VARCHAR(128) NOT NULL,
                    v3 VARCHAR(128) NOT NULL,
                    v4 VARCHAR(128) NOT NULL,
                    v5 VARCHAR(128) NOT NULL,
                    CONSTRAINT unique_key_diesel_adapter UNIQUE(ptype, v0, v1, v2, v3, v4, v5)
                );
            "#,
        TABLE_NAME
    ).as_str();
    r.exec(&sql, Vec::new()).await
}

pub async fn remove_policy(r: &Rbatis, pt: &str, rule: Vec<String>) -> Result<bool> {
    let rule = normalize_casbin_rule(rule, 0);
    let mut wa = r.new_wrapper().eq("ptype", pt);
    if !rule[0].is_empty() {
        wa = wa.eq("v0", &rule[0].clone());
    }
    if !rule[1].is_empty() {
        wa = wa.eq("v1", &rule[1].clone());
    }
    if !rule[2].is_empty() {
        wa = wa.eq("v2", &rule[2].clone());
    }
    if !rule[3].is_empty() {
        wa = wa.eq("v3", &rule[3].clone());
    }
    if !rule[4].is_empty() {
        wa = wa.eq("v4", &rule[4].clone());
    }
    if !rule[5].is_empty() {
        wa = wa.eq("v5", &rule[5].clone());
    }
    r.remove_by_wrapper::<CasbinRule>(wa).await;
    Ok(true)
}

pub async fn remove_policies(r: &Rbatis, pt: &str, rules: Vec<Vec<String>>) -> Result<bool> {
    for rule in rules {
        remove_policy(r, pt, rule).await;
    }
    Ok(true)
}

pub async fn remove_filtered_policy(
    r: &Rbatis,
    pt: &str,
    field_index: usize,
    field_values: Vec<String>,
) -> Result<bool> {
    let field_values_x = normalize_casbin_rule(field_values, field_index);
    let mut wrapper = r.new_wrapper().eq("ptype", pt);
    match field_index {
        1 => {
            if field_values_x[0].is_empty() {
                wrapper = wrapper.eq("v1", field_values_x[0].clone());
            }
            if field_values_x[1].is_empty() {
                wrapper = wrapper.eq("v2", field_values_x[1].clone());
            }
            if field_values_x[2].is_empty() {
                wrapper = wrapper.eq("v3", field_values_x[2].clone());
            }
            if field_values_x[3].is_empty() {
                wrapper = wrapper.eq("v4", field_values_x[3].clone());
            }
            if field_values_x[4].is_empty() {
                wrapper = wrapper.eq("v5", field_values_x[4].clone());
            }
        }
        2 => {
            if field_values_x[0].is_empty() {
                wrapper = wrapper.eq("v2", field_values_x[0].clone());
            }
            if field_values_x[1].is_empty() {
                wrapper = wrapper.eq("v3", field_values_x[1].clone());
            }
            if field_values_x[2].is_empty() {
                wrapper = wrapper.eq("v4", field_values_x[2].clone());
            }
            if field_values_x[3].is_empty() {
                wrapper = wrapper.eq("v5", field_values_x[3].clone());
            }
        }
        3 => {
            if field_values_x[0].is_empty() {
                wrapper = wrapper.eq("v3", field_values_x[0].clone());
            }
            if field_values_x[1].is_empty() {
                wrapper = wrapper.eq("v4", field_values_x[1].clone());
            }
            if field_values_x[2].is_empty() {
                wrapper = wrapper.eq("v5", field_values_x[2].clone());
            }
        }
        4 => {
            if field_values_x[0].is_empty() {
                wrapper = wrapper.eq("v4", field_values_x[0].clone());
            }
            if field_values_x[1].is_empty() {
                wrapper = wrapper.eq("v5", field_values_x[1].clone());
            }
        }
        5 => {
            if field_values_x[0].is_empty() {
                wrapper = wrapper.eq("v5", field_values_x[0].clone());
            }
        }
        _ => {
            if field_values_x[0].is_empty() {
                wrapper = wrapper.eq("v0", field_values_x[0].clone());
            }
            if field_values_x[1].is_empty() {
                wrapper = wrapper.eq("v1", field_values_x[1].clone());
            }
            if field_values_x[2].is_empty() {
                wrapper = wrapper.eq("v2", field_values_x[2].clone());
            }
            if field_values_x[3].is_empty() {
                wrapper = wrapper.eq("v3", field_values_x[3].clone());
            }
            if field_values_x[4].is_empty() {
                wrapper = wrapper.eq("v4", field_values_x[4].clone());
            }
            if field_values_x[5].is_empty() {
                wrapper = wrapper.eq("v5", field_values_x[5].clone());
            }
        }
    }
    r.remove_by_wrapper::<CasbinRule>(wrapper).await;
    Ok(true)
}

pub(crate) async fn clear_policy(r: &Rbatis) -> Result<()> {
    r.remove_by_wrapper::<CasbinRule>(r.new_wrapper()).await;
    Ok(())
}

pub(crate) async fn save_policy(r: &Rbatis, rules: Vec<CasbinRule>) -> Result<()> {
    let mut tx = r.acquire_begin().await.unwrap();
    for rule in rules {
        let v1 = rule.v1;
        let v2 = rule.v2;
        let v3 = rule.v3;
        let v4 = rule.v4;
        let v5 = rule.v5;
        let gwrapper = r
            .new_wrapper()
            .eq("ptype", rule.ptype.clone())
            .eq("v0", rule.v0.clone())
            .eq("v1", v1.clone())
            .eq("v2", v2.clone())
            .eq("v3", v3.clone())
            .eq("v4", v4.clone())
            .eq("v5", v5.clone());
        let s = tx.fetch_by_wrapper::<CasbinRule>(gwrapper).await;
        if s.is_ok() {
            continue;
        }
        let e = CasbinRule {
            id: None,
            ptype: rule.ptype.to_string(),
            v0: rule.v0.to_string(),
            v1: v1.to_string(),
            v2: v2.to_string(),
            v3: v3.to_string(),
            v4: v4.to_string(),
            v5: v5.to_string(),
        };
        tx.save::<CasbinRule>(&e, &[Skip::Column("id")]).await;
    }
    tx.commit().await.unwrap();
    Ok(())
}

pub(crate) async fn load_policy(r: &Rbatis) -> Result<Vec<CasbinRule>> {
    r.fetch_list_by_wrapper::<CasbinRule>(r.new_wrapper())
        .await
        .map_err(|e| casbin::error::Error::IoError(e.into()))
}

pub(crate) async fn add_policy(r: &Rbatis, rule: CasbinRule) -> Result<bool> {
    let mut re = Vec::new();
    re.push(rule);
    save_policy(r, re).await;
    Ok(true)
}

pub(crate) async fn add_policies(r: &Rbatis, new_rules: Vec<CasbinRule>) -> Result<bool> {
    new_rules.into_iter().map(|rule| async { add_policy(r, rule).await });
    Ok(true)
}

fn normalize_casbin_rule(mut rule: Vec<String>, field_index: usize) -> Vec<String> {
    rule.resize(6 - field_index, String::from(""));
    rule
}

#[cfg(test)]
mod tests {
    use crate::actions::new;
    use crate::actions::ADAPTER_RB;

    #[tokio::test]
    async fn it_works() {
        ADAPTER_RB.link("mysql://root:123456@localhost:3306/casbin2").await.unwrap();
        let _r = new(&ADAPTER_RB).await;
    }
}